import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
import {
  mockVectorInsert,
  mockVectorDelete,
  mockVectorEmbRecall,
  mockVectorInit,
  mockGetVectorDataByTime,
  mockGetVectorCountByTeamId,
  mockGetVectorCountByDatasetId,
  mockGetVectorCountByCollectionId,
  resetVectorMocks
} from '@test/mocks/common/vector';
import { mockGetVectorsByText } from '@test/mocks/core/ai/embedding';

// Import controller functions after mocks are set up
import {
  initVectorStore,
  recallFromVectorStore,
  getVectorDataByTime,
  getVectorCountByTeamId,
  getVectorCountByDatasetId,
  getVectorCountByCollectionId,
  insertDatasetDataVector,
  deleteDatasetDataVector
} from '@fastgpt/service/common/vectorDB/controller';

// Mock redis cache functions
const mockGetRedisCache = vi.fn();
const mockSetRedisCache = vi.fn();
const mockDelRedisCache = vi.fn();
const mockIncrValueToCache = vi.fn();

vi.mock('@fastgpt/service/common/redis/cache', () => ({
  setRedisCache: (...args: any[]) => mockSetRedisCache(...args),
  getRedisCache: (...args: any[]) => mockGetRedisCache(...args),
  delRedisCache: (...args: any[]) => mockDelRedisCache(...args),
  incrValueToCache: (...args: any[]) => mockIncrValueToCache(...args),
  CacheKeyEnum: {
    team_vector_count: 'team_vector_count',
    team_point_surplus: 'team_point_surplus',
    team_point_total: 'team_point_total'
  },
  CacheKeyEnumTime: {
    team_vector_count: 1800,
    team_point_surplus: 60,
    team_point_total: 60
  }
}));

describe('VectorDB Controller', () => {
  beforeEach(() => {
    resetVectorMocks();
    mockGetRedisCache.mockReset();
    mockSetRedisCache.mockReset();
    mockDelRedisCache.mockReset();
    mockIncrValueToCache.mockReset();
    mockGetVectorsByText.mockClear();
  });

  describe('initVectorStore', () => {
    it('should call Vector.init', async () => {
      await initVectorStore();
      expect(mockVectorInit).toHaveBeenCalled();
    });
  });

  describe('recallFromVectorStore', () => {
    it('should call Vector.embRecall with correct props', async () => {
      const props = {
        teamId: 'team_123',
        datasetIds: ['dataset_1', 'dataset_2'],
        vector: [0.1, 0.2, 0.3],
        limit: 10,
        forbidCollectionIdList: ['col_forbidden']
      };

      const result = await recallFromVectorStore(props);

      expect(mockVectorEmbRecall).toHaveBeenCalledWith(props);
      expect(result).toEqual({
        results: [
          { id: '1', collectionId: 'col_1', score: 0.95 },
          { id: '2', collectionId: 'col_2', score: 0.85 }
        ]
      });
    });

    it('should handle filterCollectionIdList', async () => {
      const props = {
        teamId: 'team_123',
        datasetIds: ['dataset_1'],
        vector: [0.1, 0.2],
        limit: 5,
        forbidCollectionIdList: [],
        filterCollectionIdList: ['col_1', 'col_2']
      };

      await recallFromVectorStore(props);

      expect(mockVectorEmbRecall).toHaveBeenCalledWith(props);
    });
  });

  describe('getVectorDataByTime', () => {
    it('should call Vector.getVectorDataByTime with correct date range', async () => {
      const start = new Date('2024-01-01');
      const end = new Date('2024-01-31');

      const result = await getVectorDataByTime(start, end);

      expect(mockGetVectorDataByTime).toHaveBeenCalledWith(start, end);
      expect(result).toEqual([
        { id: '1', teamId: 'team_1', datasetId: 'dataset_1' },
        { id: '2', teamId: 'team_1', datasetId: 'dataset_2' }
      ]);
    });
  });

  describe('getVectorCountByTeamId', () => {
    it('should return cached count if available', async () => {
      mockGetRedisCache.mockResolvedValue('150');

      const result = await getVectorCountByTeamId('team_123');

      expect(result).toBe(150);
      expect(mockGetVectorCountByTeamId).not.toHaveBeenCalled();
    });

    it('should fetch from Vector and cache if no cache exists', async () => {
      mockGetRedisCache.mockResolvedValue(null);
      mockGetVectorCountByTeamId.mockResolvedValue(200);

      const result = await getVectorCountByTeamId('team_456');

      expect(result).toBe(200);
      expect(mockGetVectorCountByTeamId).toHaveBeenCalledWith('team_456');
    });

    it('should handle undefined cache value', async () => {
      mockGetRedisCache.mockResolvedValue(undefined);
      mockGetVectorCountByTeamId.mockResolvedValue(50);

      const result = await getVectorCountByTeamId('team_789');

      expect(result).toBe(50);
      expect(mockGetVectorCountByTeamId).toHaveBeenCalled();
    });
  });

  describe('getVectorCountByDatasetId', () => {
    it('should call Vector.getVectorCountByDatasetId', async () => {
      const result = await getVectorCountByDatasetId('team_1', 'dataset_1');

      expect(mockGetVectorCountByDatasetId).toHaveBeenCalledWith('team_1', 'dataset_1');
      expect(result).toBe(50);
    });
  });

  describe('getVectorCountByCollectionId', () => {
    it('should call Vector.getVectorCountByCollectionId', async () => {
      const result = await getVectorCountByCollectionId('team_1', 'dataset_1', 'col_1');

      expect(mockGetVectorCountByCollectionId).toHaveBeenCalledWith('team_1', 'dataset_1', 'col_1');
      expect(result).toBe(25);
    });
  });

  describe('insertDatasetDataVector', () => {
    const mockModel = {
      model: 'text-embedding-ada-002',
      name: 'text-embedding-ada-002',
      charsPointsPrice: 0,
      maxToken: 8192,
      weight: 100,
      defaultToken: 512,
      dbConfig: {},
      queryExtensionModel: ''
    };

    it('should generate embeddings and insert vectors', async () => {
      const mockVectors = [
        [0.1, 0.2],
        [0.3, 0.4]
      ];
      mockGetVectorsByText.mockResolvedValue({
        tokens: 100,
        vectors: mockVectors
      });
      mockVectorInsert.mockResolvedValue({
        insertIds: ['id_1', 'id_2']
      });

      const result = await insertDatasetDataVector({
        teamId: 'team_123',
        datasetId: 'dataset_456',
        collectionId: 'col_789',
        inputs: ['hello world', 'test text'],
        model: mockModel as any
      });

      expect(mockGetVectorsByText).toHaveBeenCalledWith({
        model: mockModel,
        input: ['hello world', 'test text'],
        type: 'db'
      });
      expect(mockVectorInsert).toHaveBeenCalledWith({
        teamId: 'team_123',
        datasetId: 'dataset_456',
        collectionId: 'col_789',
        vectors: mockVectors
      });
      expect(result).toEqual({
        tokens: 100,
        insertIds: ['id_1', 'id_2']
      });
    });

    it('should increment team vector cache', async () => {
      mockGetVectorsByText.mockResolvedValue({
        tokens: 50,
        vectors: [[0.1]]
      });
      mockVectorInsert.mockResolvedValue({
        insertIds: ['id_1']
      });

      await insertDatasetDataVector({
        teamId: 'team_abc',
        datasetId: 'dataset_def',
        collectionId: 'col_ghi',
        inputs: ['single input'],
        model: mockModel as any
      });

      // Cache increment is called asynchronously
      await new Promise((resolve) => setTimeout(resolve, 10));
      expect(mockIncrValueToCache).toHaveBeenCalled();
    });

    it('should handle empty inputs', async () => {
      mockGetVectorsByText.mockResolvedValue({
        tokens: 0,
        vectors: []
      });
      mockVectorInsert.mockResolvedValue({
        insertIds: []
      });

      const result = await insertDatasetDataVector({
        teamId: 'team_123',
        datasetId: 'dataset_456',
        collectionId: 'col_789',
        inputs: [],
        model: mockModel as any
      });

      expect(result).toEqual({
        tokens: 0,
        insertIds: []
      });
    });
  });

  describe('deleteDatasetDataVector', () => {
    it('should delete by single id', async () => {
      const props = {
        teamId: 'team_123',
        id: 'vector_id_1'
      };

      await deleteDatasetDataVector(props);

      expect(mockVectorDelete).toHaveBeenCalledWith(props);
    });

    it('should delete by datasetIds', async () => {
      const props = {
        teamId: 'team_123',
        datasetIds: ['dataset_1', 'dataset_2']
      };

      await deleteDatasetDataVector(props);

      expect(mockVectorDelete).toHaveBeenCalledWith(props);
    });

    it('should delete by datasetIds and collectionIds', async () => {
      const props = {
        teamId: 'team_123',
        datasetIds: ['dataset_1'],
        collectionIds: ['col_1', 'col_2']
      };

      await deleteDatasetDataVector(props);

      expect(mockVectorDelete).toHaveBeenCalledWith(props);
    });

    it('should delete by idList', async () => {
      const props = {
        teamId: 'team_123',
        idList: ['id_1', 'id_2', 'id_3']
      };

      await deleteDatasetDataVector(props);

      expect(mockVectorDelete).toHaveBeenCalledWith(props);
    });

    it('should call delete and return result', async () => {
      mockVectorDelete.mockResolvedValue({ deletedCount: 5 });

      const props = {
        teamId: 'team_cache_test',
        id: 'some_id'
      };

      const result = await deleteDatasetDataVector(props);

      expect(mockVectorDelete).toHaveBeenCalledWith(props);
      expect(result).toEqual({ deletedCount: 5 });
    });
  });
});
